<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Pdf
 * @subpackage FileParser
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: OpenType.php 18993 2009-11-15 17:09:16Z alexander $
 */

/** Zend_Pdf_FileParser_Font */
require_once 'Zend/Pdf/FileParser/Font.php';

/**
 * Abstract base class for OpenType font file parsers.
 *
 * TrueType was originally developed by Apple and was adopted as the default
 * font format for the Microsoft Windows platform. OpenType is an extension of
 * TrueType, developed jointly by Microsoft and Adobe, which adds support for
 * PostScript font data.
 *
 * This abstract parser class forms the foundation for concrete subclasses which
 * extract either TrueType or PostScript font data from the file.
 *
 * All OpenType files use big-endian byte ordering.
 *
 * The full TrueType and OpenType specifications can be found at:
 * <ul>
 *  <li>{@link http://developer.apple.com/textfonts/TTRefMan/}
 *  <li>{@link http://www.microsoft.com/typography/OTSPEC/}
 *  <li>{@link http://partners.adobe.com/public/developer/opentype/index_spec.html}
 * </ul>
 *
 * @package    Zend_Pdf
 * @subpackage FileParser
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
abstract class Zend_Pdf_FileParser_Font_OpenType extends Zend_Pdf_FileParser_Font
{
	/**** Instance Variables ****/


	/**
	 * Stores the scaler type (font type) for the font file. See
	 * {@link _readScalerType()}.
	 * @var integer
	 */
	protected $_scalerType = 0;

	/**
	 * Stores the byte offsets to the various information tables.
	 * @var array
	 */
	protected $_tableDirectory = array();



	/**** Public Interface ****/


	/* Semi-Concrete Class Implementation */

	/**
	 * Verifies that the font file is in the expected format.
	 *
	 * NOTE: This method should be overridden in subclasses to check the
	 * specific format and set $this->_isScreened!
	 *
	 * @throws Zend_Pdf_Exception
	 */
	public function screen()
	{
		if ($this->_isScreened) {
			return;
		}
		$this->_readScalerType();
	}

	/**
	 * Reads and parses the font data from the file on disk.
	 *
	 * NOTE: This method should be overridden in subclasses to add type-
	 * specific parsing and set $this->isParsed.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	public function parse()
	{
		if ($this->_isParsed) {
			return;
		}

		/* Screen the font file first, if it hasn't been done yet.
		 */
		$this->screen();

		/* Start by reading the table directory.
		 */
		$this->_parseTableDirectory();

		/* Then parse all of the required tables.
		 */
		$this->_parseHeadTable();
		$this->_parseNameTable();
		$this->_parsePostTable();
		$this->_parseHheaTable();
		$this->_parseMaxpTable();
		$this->_parseOs2Table();
		$this->_parseHmtxTable();
		$this->_parseCmapTable();

		/* If present, parse the optional tables.
		 */
		/**
		 * @todo Add parser for kerning pairs.
		 * @todo Add parser for ligatures.
		 * @todo Add parser for other useful hinting tables.
		 */
	}



	/**** Internal Methods ****/


	/* Parser Methods */

	/**
	 * Parses the OpenType table directory.
	 *
	 * The table directory contains the identifier, checksum, byte offset, and
	 * length of each of the information tables housed in the font file.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseTableDirectory()
	{
		$this->moveToOffset(4);

		$tableCount = $this->readUInt(2);
		$this->_debugLog('%d tables', $tableCount);

		/* Sanity check, in case we're not actually reading a OpenType file and
		 * the first four bytes coincidentally matched an OpenType signature in
		 * screen() above.
		 *
		 * There are at minimum 7 required tables: cmap, head, hhea, hmtx, maxp,
		 * name, and post. In the current OpenType standard, only 32 table types
		 * are defined, so use 50 as a practical limit.
		 */
		if (($tableCount < 7) || ($tableCount > 50)) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('Table count not within expected range',
			Zend_Pdf_Exception::BAD_TABLE_COUNT);
		}

		/* Skip the next 6 bytes, which contain values to aid a binary search.
		 */
		$this->skipBytes(6);

		/* The directory contains four values: the name of the table, checksum,
		 * offset to the table from the beginning of the font, and actual data
		 * length of the table.
		 */
		for ($tableIndex = 0; $tableIndex < $tableCount; $tableIndex++) {
			$tableName = $this->readBytes(4);

			/* We ignore the checksum here for two reasons: First, the PDF viewer
			 * will do this later anyway; Second, calculating the checksum would
			 * require unsigned integers, which PHP does not currently provide.
			 * We may revisit this in the future.
			 */
			$this->skipBytes(4);

			$tableOffset = $this->readUInt(4);
			$tableLength = $this->readUInt(4);
			$this->_debugLog('%s offset: 0x%x; length: %d', $tableName, $tableOffset, $tableLength);

			/* Sanity checks for offset and length values.
			 */
			$fileSize = $this->_dataSource->getSize();
			if (($tableOffset < 0) || ($tableOffset > $fileSize)) {
				require_once 'Zend/Pdf/Exception.php';
				throw new Zend_Pdf_Exception("Table offset ($tableOffset) not within expected range",
				Zend_Pdf_Exception::INDEX_OUT_OF_RANGE);
			}
			if (($tableLength < 0) || (($tableOffset + $tableLength) > $fileSize)) {
				require_once 'Zend/Pdf/Exception.php';
				throw new Zend_Pdf_Exception("Table length ($tableLength) not within expected range",
				Zend_Pdf_Exception::INDEX_OUT_OF_RANGE);
			}

			$this->_tableDirectory[$tableName]['offset'] = $tableOffset;
			$this->_tableDirectory[$tableName]['length'] = $tableLength;
		}
	}


	/**
	 * Parses the OpenType head (Font Header) table.
	 *
	 * The head table contains global information about the font such as the
	 * revision number and global metrics.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseHeadTable()
	{
		$this->_jumpToTable('head');

		/* We can read any version 1 table.
		 */
		$tableVersion = $this->_readTableVersion(1, 1);

		/* Skip the font revision number and checksum adjustment.
		 */
		$this->skipBytes(8);

		$magicNumber = $this->readUInt(4);
		if ($magicNumber != 0x5f0f3cf5) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('Wrong magic number. Expected: 0x5f0f3cf5; actual: '
			. sprintf('%x', $magicNumber),
			Zend_Pdf_Exception::BAD_MAGIC_NUMBER);
		}

		/* Most of the flags we ignore, but there are a few values that are
		 * useful for our layout routines.
		 */
		$flags = $this->readUInt(2);
		$this->baselineAtZero    = $this->isBitSet(0, $flags);
		$this->useIntegerScaling = $this->isBitSet(3, $flags);

		$this->unitsPerEm = $this->readUInt(2);
		$this->_debugLog('Units per em: %d', $this->unitsPerEm);

		/* Skip creation and modification date/time.
		 */
		$this->skipBytes(16);

		$this->xMin = $this->readInt(2);
		$this->yMin = $this->readInt(2);
		$this->xMax = $this->readInt(2);
		$this->yMax = $this->readInt(2);
		$this->_debugLog('Font bounding box: %d %d %d %d',
		$this->xMin, $this->yMin, $this->xMax, $this->yMax);

		/* The style bits here must match the fsSelection bits in the OS/2
		 * table, if present.
		 */
		$macStyleBits = $this->readUInt(2);
		$this->isBold   = $this->isBitSet(0, $macStyleBits);
		$this->isItalic = $this->isBitSet(1, $macStyleBits);

		/* We don't need the remainder of data in this table: smallest readable
		 * size, font direction hint, indexToLocFormat, and glyphDataFormat.
		 */
	}


	/**
	 * Parses the OpenType name (Naming) table.
	 *
	 * The name table contains all of the identifying strings associated with
	 * the font such as its name, copyright, trademark, license, etc.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseNameTable()
	{
		$this->_jumpToTable('name');
		$baseOffset = $this->_tableDirectory['name']['offset'];

		/* The name table begins with a short header, followed by each of the
		 * fixed-length name records, followed by the variable-length strings.
		 */

		/* We only understand version 0 tables.
		 */
		$tableFormat = $this->readUInt(2);
		if ($tableFormat != 0) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("Unable to read format $tableFormat table",
			Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
		}
		$this->_debugLog('Format %d table', $tableFormat);

		$nameCount = $this->readUInt(2);
		$this->_debugLog('%d name strings', $nameCount);

		$storageOffset = $this->readUInt(2) + $baseOffset;
		$this->_debugLog('Storage offset: 0x%x', $storageOffset);

		/* Scan the name records for those we're interested in. We'll skip over
		 * encodings and languages we don't understand or support. Prefer the
		 * Microsoft Unicode encoding for a given name/language combination, but
		 * use Mac Roman if nothing else is available. We will extract the
		 * actual strings later.
		 */
		$nameRecords = array();
		for ($nameIndex = 0; $nameIndex < $nameCount; $nameIndex++) {

			$platformID = $this->readUInt(2);
			$encodingID = $this->readUInt(2);

			if (! ( (($platformID == 3) && ($encodingID == 1)) ||    // Microsoft Unicode
			(($platformID == 1) && ($encodingID == 0))       // Mac Roman
			) ) {
				$this->skipBytes(8);    // Not a supported encoding. Move on.
				continue;
			}

			$languageID = $this->readUInt(2);
			$nameID     = $this->readUInt(2);
			$nameLength = $this->readUInt(2);
			$nameOffset = $this->readUInt(2);

			$languageCode = $this->_languageCodeForPlatform($platformID, $languageID);
			if ($languageCode === null) {
				$this->_debugLog('Skipping languageID: 0x%x; platformID %d', $languageID, $platformID);
				continue;    // Not a supported language. Move on.
			}

			$this->_debugLog('Adding nameID: %d; languageID: 0x%x; platformID: %d; offset: 0x%x (0x%x); length: %d',
			$nameID, $languageID, $platformID, $baseOffset + $nameOffset, $nameOffset, $nameLength);

			/* Entries in the name table are sorted by platform ID. If an entry
			 * exists for both Mac Roman and Microsoft Unicode, the Unicode entry
			 * will prevail since it is processed last.
			 */
			$nameRecords[$nameID][$languageCode] = array('platform' => $platformID,
                                                         'offset'   => $nameOffset,
                                                         'length'   => $nameLength );
		}

		/* Now go back and extract the interesting strings.
		 */
		$fontNames = array();
		foreach ($nameRecords as $name => $languages) {
			foreach ($languages as $language => $attributes) {
				$stringOffset = $storageOffset + $attributes['offset'];
				$this->moveToOffset($stringOffset);
				if ($attributes['platform'] == 3) {
					$string = $this->readStringUTF16($attributes['length']);
				} else {
					$string = $this->readStringMacRoman($attributes['length']);
				}
				$fontNames[$name][$language] = $string;
			}
		}

		$this->names = $fontNames;
	}


	/**
	 * Parses the OpenType post (PostScript Information) table.
	 *
	 * The post table contains additional information required for using the font
	 * on PostScript printers. It also contains the preferred location and
	 * thickness for an underline, which is used by our layout code.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parsePostTable()
	{
		$this->_jumpToTable('post');

		/* We can read versions 1-4 tables.
		 */
		$tableVersion = $this->_readTableVersion(1, 4);

		$this->italicAngle = $this->readFixed(16, 16);

		$this->underlinePosition = $this->readInt(2);
		$this->underlineThickness = $this->readInt(2);

		$fixedPitch = $this->readUInt(4);
		$this->isMonospaced = ($fixedPitch !== 0);

		/* Skip over PostScript virtual memory usage.
		 */
		$this->skipBytes(16);

		/* The format of the remainder of this table is dependent on the table
		 * version. However, since it contains glyph ordering information and
		 * PostScript names which we don't use, move on. (This may change at
		 * some point in the future though...)
		 */
	}


	/**
	 * Parses the OpenType hhea (Horizontal Header) table.
	 *
	 * The hhea table contains information used for horizontal layout. It also
	 * contains some vertical layout information for Apple systems. The vertical
	 * layout information for the PDF file is usually taken from the OS/2 table.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseHheaTable()
	{
		$this->_jumpToTable('hhea');

		/* We can read any version 1 table.
		 */
		$tableVersion = $this->_readTableVersion(1, 1);

		/* The typographic ascent, descent, and line gap values are Apple-
		 * specific. Similar values exist in the OS/2 table. We'll use these
		 * values unless better values are found in OS/2.
		 */
		$this->ascent = $this->readInt(2);
		$this->descent = $this->readInt(2);
		$this->lineGap = $this->readInt(2);

		/* The descent value is supposed to be negative--it's the distance
		 * relative to the baseline. However, some fonts improperly store a
		 * positive value in this field. If a positive value is found, flip the
		 * sign and record a warning in the debug log that we did this.
		 */
		if ($this->descent > 0) {
			$this->_debugLog('Warning: Font should specify negative descent. Actual: %d; Using %d',
			$this->descent, -$this->descent);
			$this->descent = -$this->descent;
		}

		/* Skip over advance width, left and right sidebearing, max x extent,
		 * caret slope rise, run, and offset, and the four reserved fields.
		 */
		$this->skipBytes(22);

		/* These values are needed to read the hmtx table.
		 */
		$this->metricDataFormat = $this->readInt(2);
		$this->numberHMetrics = $this->readUInt(2);
		$this->_debugLog('hmtx data format: %d; number of metrics: %d',
		$this->metricDataFormat, $this->numberHMetrics);
	}


	/**
	 * Parses the OpenType hhea (Horizontal Header) table.
	 *
	 * The hhea table contains information used for horizontal layout. It also
	 * contains some vertical layout information for Apple systems. The vertical
	 * layout information for the PDF file is usually taken from the OS/2 table.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseMaxpTable()
	{
		$this->_jumpToTable('maxp');

		/* We don't care about table version.
		 */
		$this->_readTableVersion(0, 1);

		/* The number of glyphs in the font.
		 */
		$this->numGlyphs = $this->readUInt(2);
		$this->_debugLog('number of glyphs: %d', $this->numGlyphs);

		// Skip other maxp table entries (if presented with table version 1.0)...
	}


	/**
	 * Parses the OpenType OS/2 (OS/2 and Windows Metrics) table.
	 *
	 * The OS/2 table contains additional metrics data that is required to use
	 * the font on the OS/2 or Microsoft Windows platforms. It is not required
	 * for Macintosh fonts, so may not always be present. When available, we use
	 * this table to determine most of the vertical layout and stylistic
	 * information and for the font.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseOs2Table()
	{
		if (! $this->numberHMetrics) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("hhea table must be parsed prior to calling this method",
			Zend_Pdf_Exception::PARSED_OUT_OF_ORDER);
		}

		try {
			$this->_jumpToTable('OS/2');
		} catch (Zend_Pdf_Exception $exception) {
			/* This table is not always present. If missing, use default values.
			 */
			require_once 'Zend/Pdf/Exception.php';
			if ($exception->getCode() == Zend_Pdf_Exception::REQUIRED_TABLE_NOT_FOUND) {
				$this->_debugLog('No OS/2 table found. Using default values');
				$this->fontWeight = Zend_Pdf_Font::WEIGHT_NORMAL;
				$this->fontWidth = Zend_Pdf_Font::WIDTH_NORMAL;
				$this->isEmbeddable = true;
				$this->isSubsettable = true;
				$this->strikeThickness = $this->unitsPerEm * 0.05;
				$this->strikePosition  = $this->unitsPerEm * 0.225;
				$this->isSerifFont      = false;    // the style of the font is unknown
				$this->isSansSerifFont  = false;
				$this->isOrnamentalFont = false;
				$this->isScriptFont     = false;
				$this->isSymbolicFont   = false;
				$this->isAdobeLatinSubset = false;
				$this->vendorID = '';
				$this->xHeight = 0;
				$this->capitalHeight = 0;
				return;
			} else {
				/* Something else went wrong. Throw this exception higher up the chain.
				 */
				throw $exception;
			}
		}

		/* Version 0 tables are becoming rarer these days. They are only found
		 * in older fonts.
		 *
		 * Version 1 formally defines the Unicode character range bits and adds
		 * two new fields to the end of the table.
		 *
		 * Version 2 defines several additional flags to the embedding bits
		 * (fsType field) and five new fields to the end of the table.
		 *
		 * Versions 2 and 3 are structurally identical. There are only two
		 * significant differences between the two: First, in version 3, the
		 * average character width (xAvgCharWidth field) is calculated using all
		 * non-zero width glyphs in the font instead of just the Latin lower-
		 * case alphabetic characters; this doesn't affect us. Second, in
		 * version 3, the embedding bits (fsType field) have been made mutually
		 * exclusive; see additional discusson on this below.
		 *
		 * We can understand all four of these table versions.
		 */
		$tableVersion = $this->readUInt(2);
		if (($tableVersion < 0) || ($tableVersion > 3)) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("Unable to read version $tableVersion table",
			Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
		}
		$this->_debugLog('Version %d table', $tableVersion);

		$this->averageCharWidth = $this->readInt(2);

		/* Indicates the visual weight and aspect ratio of the characters. Used
		 * primarily to logically sort fonts in lists. Also used to help choose
		 * a more appropriate substitute font when necessary. See the WEIGHT_
		 * and WIDTH_ constants defined in Zend_Pdf_Font.
		 */
		$this->fontWeight = $this->readUInt(2);
		$this->fontWidth  = $this->readUInt(2);

		/* Describes the font embedding licensing rights. We can only embed and
		 * subset a font when given explicit permission.
		 *
		 * NOTE: We always interpret these bits according to the rules defined
		 * in version 3 of this table, regardless of the actual version. This
		 * means we will perform our checks in order from the most-restrictive
		 * to the least.
		 */
		$embeddingFlags = $this->readUInt(2);
		$this->_debugLog('Embedding flags: %d', $embeddingFlags);
		if ($this->isBitSet(9, $embeddingFlags)) {
			/* Only bitmaps may be embedded. We don't have the ability to strip
			 * outlines from fonts yet, so this means no embed.
			 */
			$this->isEmbeddable = false;
		} else if ($this->isBitSet(1, $embeddingFlags)) {
			/* Restricted license embedding. We currently don't have any way to
			 * enforce this, so interpret this as no embed. This may be revised
			 * in the future...
			 */
			$this->isEmbeddable = false;
		} else {
			/* The remainder of the bit settings grant us permission to embed
			 * the font. There may be additional usage rights granted or denied
			 * but those only affect the PDF viewer application, not our code.
			 */
			$this->isEmbeddable = true;
		}
		$this->_debugLog('Font ' . ($this->isEmbeddable ? 'may' : 'may not') . ' be embedded');
		$isSubsettable = $this->isBitSet($embeddingFlags, 8);

		/* Recommended size and offset for synthesized subscript characters.
		 */
		$this->subscriptXSize = $this->readInt(2);
		$this->subscriptYSize = $this->readInt(2);
		$this->subscriptXOffset = $this->readInt(2);
		$this->subscriptYOffset = $this->readInt(2);

		/* Recommended size and offset for synthesized superscript characters.
		 */
		$this->superscriptXSize = $this->readInt(2);
		$this->superscriptYSize = $this->readInt(2);
		$this->superscriptXOffset = $this->readInt(2);
		$this->superscriptYOffset = $this->readInt(2);

		/* Size and vertical offset for the strikethrough.
		 */
		$this->strikeThickness = $this->readInt(2);
		$this->strikePosition  = $this->readInt(2);

		/* Describes the class of font: serif, sans serif, script. etc. These
		 * values are defined here:
		 *   http://www.microsoft.com/OpenType/OTSpec/ibmfc.htm
		 */
		$familyClass = ($this->readUInt(2) >> 8);    // don't care about subclass
		$this->_debugLog('Font family class: %d', $familyClass);
		$this->isSerifFont      = ((($familyClass >= 1) && ($familyClass <= 5)) ||
		($familyClass == 7));
		$this->isSansSerifFont  = ($familyClass == 8);
		$this->isOrnamentalFont = ($familyClass == 9);
		$this->isScriptFont     = ($familyClass == 10);
		$this->isSymbolicFont   = ($familyClass == 12);

		/* Skip over the PANOSE number. The interesting values for us overlap
		 * with the font family class defined above.
		 */
		$this->skipBytes(10);

		/* The Unicode range is made up of four 4-byte unsigned long integers
		 * which are used as bitfields covering a 128-bit range. Each bit
		 * represents a Unicode code block. If the bit is set, this font at
		 * least partially covers the characters in that block.
		 */
		$unicodeRange1 = $this->readUInt(4);
		$unicodeRange2 = $this->readUInt(4);
		$unicodeRange3 = $this->readUInt(4);
		$unicodeRange4 = $this->readUInt(4);
		$this->_debugLog('Unicode ranges: 0x%x 0x%x 0x%x 0x%x',
		$unicodeRange1, $unicodeRange2, $unicodeRange3, $unicodeRange4);

		/* The Unicode range is currently only used to decide if the character
		 * set covered by the font is a subset of the Adobe Latin set, meaning
		 * it only has the basic latin set. If it covers any other characters,
		 * even any of the extended latin characters, it is considered symbolic
		 * to PDF and must be described differently in the Font Descriptor.
		 */
		/**
		 * @todo Font is recognized as Adobe Latin subset font if it only contains
		 * Basic Latin characters (only bit 0 of Unicode range bits is set).
		 * Actually, other Unicode subranges like General Punctuation (bit 31) also
		 * fall into Adobe Latin characters. So this code has to be modified.
		 */
		$this->isAdobeLatinSubset = (($unicodeRange1 == 1) && ($unicodeRange2 == 0) &&
		($unicodeRange3 == 0) && ($unicodeRange4 == 0));
		$this->_debugLog(($this->isAdobeLatinSubset ? 'Is' : 'Is not') . ' a subset of Adobe Latin');

		$this->vendorID = $this->readBytes(4);

		/* Skip the font style bits. We use the values found in the 'head' table.
		 * Also skip the first Unicode and last Unicode character indicies. Our
		 * cmap implementation does not need these values.
		 */
		$this->skipBytes(6);

		/* Typographic ascender, descender, and line gap. These values are
		 * preferred to those in the 'hhea' table.
		 */
		$this->ascent = $this->readInt(2);
		$this->descent = $this->readInt(2);
		$this->lineGap = $this->readInt(2);

		/* The descent value is supposed to be negative--it's the distance
		 * relative to the baseline. However, some fonts improperly store a
		 * positive value in this field. If a positive value is found, flip the
		 * sign and record a warning in the debug log that we did this.
		 */
		if ($this->descent > 0) {
			$this->_debugLog('Warning: Font should specify negative descent. Actual: %d; Using %d',
			$this->descent, -$this->descent);
			$this->descent = -$this->descent;
		}

		/* Skip over Windows-specific ascent and descent.
		 */
		$this->skipBytes(4);

		/* Versions 0 and 1 tables do not contain the x or capital height
		 * fields. Record zero for unknown.
		 */
		if ($tableVersion < 2) {
			$this->xHeight = 0;
			$this->capitalHeight = 0;
		} else {

			/* Skip over the Windows code page coverages. We are only concerned
			 * with Unicode coverage.
			 */
			$this->skipBytes(8);

			$this->xHeight = $this->readInt(2);
			$this->capitalHeight = $this->readInt(2);

			/* Ignore the remaining fields in this table. They are Windows-specific.
			 */
		}
		/**
		 * @todo Obtain the x and capital heights from the 'glyf' table if they
		 *   haven't been supplied here instead of storing zero.
		 */
	}


	/**
	 * Parses the OpenType hmtx (Horizontal Metrics) table.
	 *
	 * The hmtx table contains the horizontal metrics for every glyph contained
	 * within the font. These are the critical values for horizontal layout of
	 * text.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseHmtxTable()
	{
		$this->_jumpToTable('hmtx');

		if (! $this->numberHMetrics) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("hhea table must be parsed prior to calling this method",
			Zend_Pdf_Exception::PARSED_OUT_OF_ORDER);
		}

		/* We only understand version 0 tables.
		 */
		if ($this->metricDataFormat != 0) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("Unable to read format $this->metricDataFormat table.",
			Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
		}

		/* The hmtx table has no header. For each glpyh in the font, it contains
		 * the glyph's advance width and its left side bearing. We don't use the
		 * left side bearing.
		 */
		$glyphWidths = array();
		for ($i = 0; $i < $this->numberHMetrics; $i++) {
			$glyphWidths[$i] = $this->readUInt(2);
			$this->skipBytes(2);
		}
		/* Populate last value for the rest of array
		 */
		while (count($glyphWidths) < $this->numGlyphs) {
			$glyphWidths[] = end($glyphWidths);
		}
		$this->glyphWidths = $glyphWidths;

		/* There is an optional table of left side bearings which is sometimes
		 * used for monospaced fonts. We don't use the left side bearing, so
		 * we can safely ignore it.
		 */
	}


	/**
	 * Parses the OpenType cmap (Character to Glyph Mapping) table.
	 *
	 * The cmap table provides the maps from character codes to font glyphs.
	 * There are usually at least two character maps in a font: Microsoft Unicode
	 * and Macintosh Roman. For very complex fonts, there may also be mappings
	 * for the characters in the Unicode Surrogates Area, which are UCS-4
	 * characters.
	 *
	 * @todo Need to rework the selection logic for picking a subtable. We should
	 *   have an explicit list of preferences, followed by a list of those that
	 *   are tolerable. Most specifically, since everything above this layer deals
	 *   in Unicode, we need to be sure to only accept format 0 MacRoman tables.
	 *
	 * @throws Zend_Pdf_Exception
	 */
	protected function _parseCmapTable()
	{
		$this->_jumpToTable('cmap');
		$baseOffset = $this->_tableDirectory['cmap']['offset'];

		/* We only understand version 0 tables.
		 */
		$tableVersion = $this->readUInt(2);
		if ($tableVersion != 0) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("Unable to read version $tableVersion table",
			Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
		}
		$this->_debugLog('Version %d table', $tableVersion);

		$subtableCount = $this->readUInt(2);
		$this->_debugLog('%d subtables', $subtableCount);

		/* Like the name table, there may be many different encoding subtables
		 * present. Ideally, we are looking for an acceptable Unicode table.
		 */
		$subtables = array();
		for ($subtableIndex = 0; $subtableIndex < $subtableCount; $subtableIndex++) {

			$platformID = $this->readUInt(2);
			$encodingID = $this->readUInt(2);

			if (! ( (($platformID == 0) && ($encodingID == 3)) ||    // Unicode 2.0 or later
			(($platformID == 0) && ($encodingID == 0)) ||    // Unicode
			(($platformID == 3) && ($encodingID == 1)) ||    // Microsoft Unicode
			(($platformID == 1) && ($encodingID == 0))       // Mac Roman
			) ) {
				$this->_debugLog('Unsupported encoding: platformID: %d; encodingID: %d; skipping',
				$platformID, $encodingID);
				$this->skipBytes(4);
				continue;
			}

			$subtableOffset = $this->readUInt(4);
			if ($subtableOffset < 0) {    // Sanity check for 4-byte unsigned on 32-bit platform
				$this->_debugLog('Offset 0x%x out of range for platformID: %d; skipping',
				$subtableOffset, $platformID);
				continue;
			}

			$this->_debugLog('Found subtable; platformID: %d; encodingID: %d; offset: 0x%x (0x%x)',
			$platformID, $encodingID, $baseOffset + $subtableOffset, $subtableOffset);

			$subtables[$platformID][$encodingID][] = $subtableOffset;
		}

		/* In preferred order, find a subtable to use.
		 */
		$offsets = array();

		/* Unicode 2.0 or later semantics
		 */
		if (isset($subtables[0][3])) {
			foreach ($subtables[0][3] as $offset) {
				$offsets[] = $offset;
			}
		}

		/* Unicode default semantics
		 */
		if (isset($subtables[0][0])) {
			foreach ($subtables[0][0] as $offset) {
				$offsets[] = $offset;
			}
		}

		/* Microsoft Unicode
		 */
		if (isset($subtables[3][1])) {
			foreach ($subtables[3][1] as $offset) {
				$offsets[] = $offset;
			}
		}

		/* Mac Roman.
		 */
		if (isset($subtables[1][0])) {
			foreach ($subtables[1][0] as $offset) {
				$offsets[] = $offset;
			}
		}

		$cmapType = -1;

		foreach ($offsets as $offset) {
			$cmapOffset = $baseOffset + $offset;
			$this->moveToOffset($cmapOffset);
			$format = $this->readUInt(2);
			$language = -1;
			switch ($format) {
				case 0x0:
					$cmapLength = $this->readUInt(2);
					$language = $this->readUInt(2);
					if ($language != 0) {
						$this->_debugLog('Type 0 cmap tables must be language-independent;'
						. ' language: %d; skipping', $language);
						continue;
					}
					break;

				case 0x4:    // break intentionally omitted
				case 0x6:
					$cmapLength = $this->readUInt(2);
					$language = $this->readUInt(2);
					if ($language != 0) {
						$this->_debugLog('Warning: cmap tables must be language-independent - this font'
						. ' may not work properly; language: %d', $language);
					}
					break;

				case 0x2:    // break intentionally omitted
				case 0x8:    // break intentionally omitted
				case 0xa:    // break intentionally omitted
				case 0xc:
					$this->_debugLog('Format: 0x%x currently unsupported; skipping', $format);
					continue;
					//$this->skipBytes(2);
					//$cmapLength = $this->readUInt(4);
					//$language = $this->readUInt(4);
					//if ($language != 0) {
					//    $this->_debugLog('Warning: cmap tables must be language-independent - this font'
						//                     . ' may not work properly; language: %d', $language);
						//}
					//break;

				default:
					$this->_debugLog('Unknown subtable format: 0x%x; skipping', $format);
					continue;
			}
			$cmapType = $format;
			break;
		}
		if ($cmapType == -1) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('Unable to find usable cmap table',
			Zend_Pdf_Exception::CANT_FIND_GOOD_CMAP);
		}

		/* Now extract the subtable data and create a Zend_Pdf_FontCmap object.
		 */
		$this->_debugLog('Using cmap type %d; offset: 0x%x; length: %d',
		$cmapType, $cmapOffset, $cmapLength);
		$this->moveToOffset($cmapOffset);
		$cmapData = $this->readBytes($cmapLength);

		require_once 'Zend/Pdf/Cmap.php';
		$this->cmap = Zend_Pdf_Cmap::cmapWithTypeData($cmapType, $cmapData);
	}


	/**
	 * Reads the scaler type from the header of the OpenType font file and
	 * returns it as an unsigned long integer.
	 *
	 * The scaler type defines the type of font: OpenType font files may contain
	 * TrueType or PostScript outlines. Throws an exception if the scaler type
	 * is not recognized.
	 *
	 * @return integer
	 * @throws Zend_Pdf_Exception
	 */
	protected function _readScalerType()
	{
		if ($this->_scalerType != 0) {
			return $this->_scalerType;
		}

		$this->moveToOffset(0);

		$this->_scalerType = $this->readUInt(4);

		switch ($this->_scalerType) {
			case 0x00010000:    // version 1.0 - Windows TrueType signature
				$this->_debugLog('Windows TrueType signature');
				break;

			case 0x74727565:    // 'true' - Macintosh TrueType signature
				$this->_debugLog('Macintosh TrueType signature');
				break;

			case 0x4f54544f:    // 'OTTO' - the CFF signature
				$this->_debugLog('PostScript CFF signature');
				break;

			case 0x74797031:    // 'typ1'
				require_once 'Zend/Pdf/Exception.php';
				throw new Zend_Pdf_Exception('Unsupported font type: PostScript in sfnt wrapper',
				Zend_Pdf_Exception::WRONG_FONT_TYPE);

			default:
				require_once 'Zend/Pdf/Exception.php';
				throw new Zend_Pdf_Exception('Not an OpenType font file',
				Zend_Pdf_Exception::WRONG_FONT_TYPE);
		}
		return $this->_scalerType;
	}

	/**
	 * Validates a given table's existence, then sets the file pointer to the
	 * start of that table.
	 *
	 * @param string $tableName
	 * @throws Zend_Pdf_Exception
	 */
	protected function _jumpToTable($tableName)
	{
		if (empty($this->_tableDirectory[$tableName])) {    // do not allow NULL or zero
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("Required table '$tableName' not found!",
			Zend_Pdf_Exception::REQUIRED_TABLE_NOT_FOUND);
		}
		$this->_debugLog("Parsing $tableName table...");
		$this->moveToOffset($this->_tableDirectory[$tableName]['offset']);
	}

	/**
	 * Reads the fixed 16.16 table version number and checks for compatibility.
	 * If the version is incompatible, throws an exception. If it is compatible,
	 * returns the version number.
	 *
	 * @param float $minVersion Minimum compatible version number.
	 * @param float $maxVertion Maximum compatible version number.
	 * @return float Table version number.
	 * @throws Zend_Pdf_Exception
	 */
	protected function _readTableVersion($minVersion, $maxVersion)
	{
		$tableVersion = $this->readFixed(16, 16);
		if (($tableVersion < $minVersion) || ($tableVersion > $maxVersion)) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception("Unable to read version $tableVersion table",
			Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
		}
		$this->_debugLog('Version %.2f table', $tableVersion);
		return $tableVersion;
	}

	/**
	 * Utility method that returns ISO 639 two-letter language codes from the
	 * TrueType platform and language ID. Returns NULL for languages that are
	 * not supported.
	 *
	 * @param integer $platformID
	 * @param integer $encodingID
	 * @return string | null
	 */
	protected function _languageCodeForPlatform($platformID, $languageID)
	{
		if ($platformID == 3) {    // Microsoft encoding.
			/* The low-order bytes specify the language, the high-order bytes
			* specify the dialect. We just care about the language. For the
			* complete list, see:
			*   http://www.microsoft.com/globaldev/reference/lcid-all.mspx
			*/
			$languageID &= 0xff;
			switch ($languageID) {
				case 0x09:
					return 'en';
				case 0x0c:
					return 'fr';
				case 0x07:
					return 'de';
				case 0x10:
					return 'it';
				case 0x13:
					return 'nl';
				case 0x1d:
					return 'sv';
				case 0x0a:
					return 'es';
				case 0x06:
					return 'da';
				case 0x16:
					return 'pt';
				case 0x14:
					return 'no';
				case 0x0d:
					return 'he';
				case 0x11:
					return 'ja';
				case 0x01:
					return 'ar';
				case 0x0b:
					return 'fi';
				case 0x08:
					return 'el';

				default:
					return null;
			}

		} else if ($platformID == 1) {    // Macintosh encoding.
			switch ($languageID) {
				case 0:
					return 'en';
				case 1:
					return 'fr';
				case 2:
					return 'de';
				case 3:
					return 'it';
				case 4:
					return 'nl';
				case 5:
					return 'sv';
				case 6:
					return 'es';
				case 7:
					return 'da';
				case 8:
					return 'pt';
				case 9:
					return 'no';
				case 10:
					return 'he';
				case 11:
					return 'ja';
				case 12:
					return 'ar';
				case 13:
					return 'fi';
				case 14:
					return 'el';

				default:
					return null;
			}

		} else {    // Unknown encoding.
			return null;
		}
	}

}
